/**
  bind.d
  dLISP

  Author: Klaus Blindert <klaus.blindert@web.de>
  Copyright (c) 2008
  All rights reserved.

    This file is part of dLISP.
    dLISP is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or
    (at your option) any later version.

    dLISP is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with dLISP; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

  Funky binding generation for DLisp.
  For a given class you can get away with the following code.

  class MyClass {

      // Some constructors.
      this() ...
      this(string name) ...

      // Some methods
      void methodName(int arg1, int arg2) ...
      string returnsString() ...

      // BINDING CODE

      // Generate the core binding
      mixin BindClass!("MYCLASS");

      // Default constructor is autogenerated
      mixin BindConstructor!(string);

      // Bind the methods.
      mixin BindMethods!(methodName,returnsString);
  }

*/

module dlisp.bind;

public {
  import dlisp.dlisp;
  import dlisp.evalhelpers : evalArgs;
  import std.utf : toUTF32, toUTF8;
  import std.stdio : writefln;
  import std.string : toupper;

  import std.boxer;
  import std.traits : ReturnType, ParameterTypeTuple;
}


class Function
{
    public:
        IDLisp dlisp;
        Cell* func;
        Context context;

        mixin BindClass!("DFunction");

    this(Cell* fun, IDLisp dl)
    {
        func = fun;
        dlisp = dl;
        context = dlisp.environment.context;
    }

    bool isValid()
    {
        return func !is null && dlisp !is null;
    }

    void call(T...)(T params)
    {
        assert( isValid );

        Cell*[] cells;

        cells ~= func;

        foreach(param; params)
           cells ~= boxValue(param);

//        dlisp.environment.pushScope(context);
        dlisp.eval(newList(cells));
        // Maybe save context?
        //dlisp.environment.saveContext();
//        dlisp.environment.popScope();
    }

    Cell* boxValue(T)(T return_value)
    {
        mixin(BoxReturnValue!(T));
    }
}

/**
  Invoke constructor of type T with parameter tuple A.
*/
T Construct(T,A...)(A a) {
  return new T(a);
}

/**
  Implements function parameter expansion for BindMethod.
  Assumes an array "Box[] args" with the correct types
  and unboxes the contents.
*/
template FunParams(alias f)
{
  const FunParams = FunParamsImpl!(0, ParameterTypeTuple!(f));
}

private template FunParamsImpl(int n, A ... )
{
    static if( A.length == 0 ) {
      const FunParamsImpl = "";
    } else {
      static if( A.length > 1 ) {
        const FunParamsImpl = FunParamsImpl!(n,A[0]) ~ "," ~ FunParamsImpl!(n+1,A[1..$]);
      } else {
//         pragma(msg,"unbox!(" ~ typeof(A[0]).stringof ~ ")(args[" ~ n.stringof ~ "])");
        const FunParamsImpl = "unbox!(" ~ typeof(A[0]).stringof~ ")(args[" ~ n.stringof ~"])";
      }
    }
}

/**
  Check whether a function has any parameters.
*/
template HasParams(alias f)
{
    const HasParams = ParameterTypeTuple!(f).length > 0;
}

/**
  Figure out the name of a method/function.
*/
template MethodName(alias f)
{
  const MethodName = (&f).stringof[1..$];
}

/**
  Invoke a method on a predefined instance "instance" and possibly bind the return value (if any)
  to an auto variable "return_value".

  Assumes an array "Box[] args" with the correct types
  and unboxes the contents.
*/
template InvokeMethod(alias func)
{
  static if( !HasParams!(func) ) {
    const InvokeMethod = BindReturnValue!(func) ~ "instance." ~ func.stringof ~ ";";
  } else {
    const InvokeMethod = BindReturnValue!(func) ~ "instance." ~ MethodName!(func) ~ "("~ FunParams!(func) ~");";
  }
}

/**
  Invoke a global function and possibly bind the return value (if any)
  to an auto variable "return_value".

  Assumes an array "Box[] args" with the correct types
  and unboxes the contents.
*/
template InvokeFunction(alias func)
{
  static if( !HasParams!(func) ) {
    const InvokeFunction = BindReturnValue!(func) ~ func.stringof ~ ";";
  } else {
    const InvokeFunction = BindReturnValue!(func) ~ MethodName!(func) ~ "("~ FunParams!(func) ~");";
  }
}

/**
  Invoke a constructor of a class "classname" and bind the returned instance
  to a variable "instance".

  Assumes an array "Box[] args" with the correct types
  and unboxes the contents.
*/
template InvokeConstructor(string classname,alias func)
{
  const InvokeConstructor = classname ~ " instance = new " ~ classname ~ "("~ FunParams!(func) ~");";
}

/**
  Check whether a function has a return type or is a void function.
*/
template HasReturnType(alias func)
{
  const HasReturnType = !is(ReturnType!(func) == void);
}

/**
  Generate a prefix "auto return_value = " for the "Invoke*" templates
  if the function has a return type.
*/
template BindReturnValue(alias func)
{
  static if( !HasReturnType!(func) )
    const BindReturnValue = "";
  else
    const BindReturnValue = "auto return_value = ";
}

/**
  Unwrap arguments from an array of DLisp Cells assumed
  to be named "cargs" into an array "Box[] args".

  Accepts a ParameterTypeTuple to use template specialization
  to correctly unwrap the contents of the DLisp cells.

  Generates code for typesafe execution and the result will
  throw an "ArgumentState" exception if a cell cannot
  be unwrapped correctly.

  Note that instances of bound classes can be wrapped
  and unwrapped automatically and typesafe.
*/
template BoxArguments(T ...)
{
  const BoxArguments = BoxArgumentsImpl!(0,T);
}

private template BoxArgumentsImpl(int n, T ...)
{
  static if( T.length == 0)
    const BoxArgumentsImpl = "";
  else static if( T.length == 1 )
    const BoxArgumentsImpl = BoxArgument!(n,T[0]);
  else
    const BoxArgumentsImpl = BoxArgument!(n,T[0]) ~ BoxArgumentsImpl!(n+1,T[1..$]);
}

template CArg(int n)
{
  const CArg = "cargs[" ~ n.stringof ~ "]";
}

template AddArgument(string argvalue)
{ 
    const AddArgument = "args ~=" ~ argvalue ~ ";";
}

template CheckArgument(int n, string checkfun)
{
    const CheckArgument = 
      "if( !" ~ checkfun ~ "("~ CArg!(n) ~") ) { "
        ~ "throw new ArgumentState(\"Expected " ~ checkfun ~ "\",cell.pos);"
        ~ "}";//"
}

template CheckArgumentAlternatives(int n, string checkfun1, string checkfun2)
{
    const CheckArgumentAlternatives = 
      "if( !" ~ checkfun1 ~ "("~ CArg!(n) ~")  && !" ~ checkfun2 ~ "("~ CArg!(n) ~") ) { "
        ~ "throw new ArgumentState(\"Expected " ~ checkfun1 ~ " or " ~ checkfun2 ~ "\",cell.pos);"
        ~ "}";//"
}

template BoxArgument(int n, T : Cell*)
{
  const BoxArgument = AddArgument!("box(" ~ CArg!(n) ~ ")");
}

template BoxArgument(int n, T : Function)
{
  const BoxArgument = CheckArgumentAlternatives!(n,"isFunc","isNil") ~ AddArgument!("box(new Function(" ~ CArg!(n) ~ ",dlisp))");
}

template BoxArgument(int n, T : int)
{
  const BoxArgument = CheckArgument!(n,"isInt") ~ AddArgument!("box(" ~ CArg!(n) ~ ".intValue)");
}

template BoxArgument(int n, T : uint)
{
  const BoxArgument = CheckArgument!(n,"isInt") ~ AddArgument!("box(" ~ CArg!(n) ~ ".intValue)");
}

template BoxArgument(int n, T : long)
{
  const BoxArgument = CheckArgument!(n,"isInt") ~ AddArgument!("box(" ~ CArg!(n) ~ ".intValue)");
}

template BoxArgument(int n, T : float)
{
  const BoxArgument = CheckArgument!(n,"isFloat") ~ AddArgument!("box(" ~ CArg!(n) ~ ".floatValue)");
}

template BoxArgument(int n, T : string)
{
  const BoxArgument = CheckArgument!(n,"isString") ~ AddArgument!("box(" ~ CArg!(n) ~ ".strValue)");
}

template BoxArgument(int n, T : dstring)
{
  const BoxArgument = CheckArgument!(n,"isString") ~ AddArgument!("box(toUTF32(" ~ CArg!(n) ~ ".strValue))");
}

template BoxArgument(int n, T : bool)
{
  const BoxArgument =  AddArgument!("box(isTrue(" ~ CArg!(n) ~ "))");
}

template BoxArgument(int n, T)
{
  static if( IsBoundClass!(T) ) {
    const BoxArgument =  CheckArgument!(n,T.stringof ~ ".isInstance") ~ AddArgument!(CArg!(n) ~ ".instance");
  } else {
    pragma(msg,"DON'T KNOW HOW TO AUTOMATICALLY BIND THIS: " ~ T.stringof);//"
    static assert(0);
  }
}

template BoxReturnValue(T : Cell*)
{
  const BoxReturnValue = "return return_value;";
}

template BoxReturnValue(T : Function)
{
  const BoxReturnValue = "return return_value.func;";
}

template BoxReturnValue(T : int)
{
  const BoxReturnValue = "return newInt(return_value);";
}

template BoxReturnValue(T : uint)
{
  const BoxReturnValue = "return newInt(return_value);";
}

template BoxReturnValue(T : long)
{
  const BoxReturnValue = "return newInt(return_value);";
}

template BoxReturnValue(T : float)
{
  const BoxReturnValue = "return newFloat(return_value);";
}

template BoxReturnValue(T : bool)
{
  const BoxReturnValue = "return newBool(return_value);";
}

template BoxReturnValue(T : string)
{
  const BoxReturnValue = "return newStr(return_value);";
}

template BoxReturnValue(T : dstring)
{
  const BoxReturnValue = "return newStr(toUTF8(return_value));";
}

template BoxReturnValue(T : T[])
{
  pragma(msg,"DLISP.BIND: Fugly array boxing.");//"
  const BoxReturnValue = "
    Cell* _cell_[];
    foreach(typeof(return_value[0]) item; return_value)
    {
      Cell* next_item () {
      auto return_value = item;
      " ~ BoxReturnValue!(typeof(T)) ~ "
      };
      _cell_ ~= next_item();
    }
    return newList(_cell_);
  ";
}

/**
  Wrap a existing variable "return_value" into a DLisp Cell
  and return it.

  Accepts the type of "return_value" and uses template specialization
  to correctly wrap the contents into a DLisp cell.

  Note that instances of bound classes can be wrapped
  and unwrapped automatically and typesafe.
*/
template BoxReturnValue(T)
{
  static if ( IsBoundClass!(T) ) {
    const BoxReturnValue = "if( return_value !is null ){ return return_value.wrap; }else{ return null; };"; 
  } else {
    pragma(msg,"DON'T KNOW HOW TO AUTOMATICALLY WRAP RETURN TYPE: " ~ T.stringof);
    static assert(0);
  }
}

/**
  Generate binding code for a D class.

  New static functions:

    void bindClass(Environment environment)
      Bind the class into the dlisp environment.
      This is only necessary if you want the class object
      available to subclass it or instantiate it from
      within DLisp.

    bool isInstance(Cell* cell)
      Check whether a given DLisp Cell contains an instance of this class.

    ClassType getInstance(Cell* cell)
      Unwrap given DLisp Cell an return the instance of this class.

    Cell* wrapInstance(DLisp dlisp, ClassType instance);
      Create a new DLisp Cell with the instance wrapped up.
      If the instance was wrapped before, return that cell.

    Cell* createInstance(DLisp dlisp, Cell* object, Cell* cell)

    Cell* getClass()
      Return the DLisp Cell containing the class object of
      the bound class.

  New functions:

    Cell* wrap(DLisp dlisp)
      Return ClassType.wrapInstance(dlisp,this)

  If you just want to pass around objects of this type,
  you are done. DLisp binding code will now be able
  to return and accept instances of the bound class.

*/
template BindClass(string classname)
{
  private Cell* _instanceCell;

  private static Cell* _classCell;
  private static Cell*[string] _methods;
  private static typeof(this) function(IDLisp,Cell*) _constructor;

  static void bindClass(IEnvironment environment)
  {
      environment[classname] = getClass();
      foreach(string key, Cell* method; _methods)
      {
        environment.addGeneric(key);
        writefln("BINDING GENERIC: ", key);
      }
  }

  void bindInstance(IEnvironment environment, string name)
  {
      bindClass(environment);
      environment[name] = wrap;
  }

  static bool isInstance(Cell* cell)
  {
    if( cell is null )
      return false;
    if( !isObject(cell) )
      return false;
    return cell.instance.unboxable(typeid(typeof(this)));
  }

  static Cell* wrapInstance(typeof(this) instance)
  {
    if( instance._instanceCell )
      return instance._instanceCell;
    Cell* object = newObject("INSTANCE OF CLASS " ~ toupper(classname),getClass());
    object.instance = box(instance);
    return object;
  }

  Cell* wrap()
  {
    return typeof(this).wrapInstance(this);
  }

  static typeof(this) createInstance(IDLisp dlisp,Cell* object, Cell* cell)
  {
    typeof(this) instance;
    //writefln("CONSTRUCTOR ARGS: %s",cellToString(cell.cdr.cdr));
    if( cell.cdr.cdr is null )
    {
      static if (is(typeof(new typeof(this)))) {
        instance = new typeof(this);
      } else {
        throw new ArgumentState("No default constructor given for " ~ classname,cell.pos);
      }
    } else {
      instance = _constructor(dlisp, cell);
    }

    instance._instanceCell = object;
    return instance;
  }

  static typeof(this) getInstance(Cell* cell)
  {
    assert(isInstance(cell));
    return unbox!(typeof(this))(cell.instance);
  }

  static Cell* makeInstance(IDLisp dlisp, Cell* cell)
  {
     Cell* object = newObject("INSTANCE OF CLASS " ~ toupper(classname),getClass());
     object.instance = box(createInstance(dlisp, object, cell));
     //writefln("CONSTRUCTED THIS: %s <-> ",cellToString(object),unbox!(typeof(this))(object.instance));
     return object;
  }

  static Cell* castToClass(IDLisp dlisp, Cell* cell)
  {
    Cell* cells[] = evalArgs(dlisp,"OO",cell.cdr);
     if( !isInstance(cells[1]) )
        throw new ArgumentState("Can not cast " ~ cellToString(cells[1]),cell.pos);
     return wrapInstance(getInstance(cells[1]));
  }

  static Cell* canCastToClass(IDLisp dlisp, Cell* cell)
  {
    Cell* cells[] = evalArgs(dlisp,"OO",cell.cdr);
     if( !isInstance(cells[1]) )
        return null;
     return newSym("T");
  }

  static Cell* getClass()
  {

    if( _classCell is null )
    {
      _classCell = newObject(classname);
      _methods["MAKE-INSTANCE"] = newPredef("MAKE-INSTANCE",toDelegate(&makeInstance),"CREATES AN INSTANCE OF " ~ toupper(classname));
      _methods["CAST"] = newPredef("CAST",toDelegate(&castToClass),"CASTS INTO AN INSTANCE OF " ~ toupper(classname));
      _methods["IS-INSTANCE"] = newPredef("IS-INSTANCE",toDelegate(&canCastToClass),"CAN BE CAST INTO AN INSTANCE OF " ~ toupper(classname));

      foreach(string name, Cell* method; _methods)
      {
        _classCell.table[name] = method;
      }
      static if(is( typeof(this) parent == super ))
      {
        static if ( IsBoundClass!(parent[0]) ) {
            pragma(msg,"DLISP.BIND: " ~ typeof(this).stringof ~ " -> " ~ parent.stringof);
            _classCell.table["PARENT"] = parent[0].getClass();
        }
      }
    }
    return _classCell;
  }
}

/**
  Check whether a type T is a bound class.
*/
template IsBoundClass(T)
{
  const IsBoundClass = is( typeof( T.bindClass ) );
}

unittest {
  //
  // Internal compiler error with GDC :(
  //

  static class UTest1 {
    mixin BindClass!("UTest");
  }
  static class UTest2 { }

  static assert(  IsBoundClass!(UTest1) );
  static assert( !IsBoundClass!(UTest2) );
}

// Incomplete Constructor Wrapper
template BindConstructor(T)
{
    static this()
    {
      static typeof(this) initWrapper(IDLisp dlisp, Cell* cell)
      {
        T func;
        // pragma(msg,T.stringof);
        // writefln ("CONSTRUCTOR: %s ARGS: %s", T.stringof, cellToString(cell.cdr.cdr));

        if(ParameterTypeTuple!(func).length > listLen(cell.cdr.cdr))
        {
          return null;
        }

        if(ParameterTypeTuple!(func).length < listLen(cell.cdr.cdr))
        {
          return null;
        }

        Cell*[] cargs = evalArgs(dlisp,cell.cdr.cdr);

        Box[] args;
        try {
          mixin(BoxArguments!(ParameterTypeTuple!(func)));
        } catch (ErrorState e) {
          return null;
        }
        mixin(InvokeConstructor!(ReturnType!(func).stringof,func));
        return instance;
      }

      _constructor = &initWrapper;
    }
}

template BindHandler(string settername, string gettername, string fname, alias func)
{
    mixin("private Function m__" ~ settername ~ ";");
    mixin("public " ~ ReturnType!(func).stringof ~ " " ~ settername ~ "(Function cb) { m__" ~ settername ~ " = cb; }");
    mixin("public Function " ~ gettername ~ "() { return m__" ~ settername ~ "; }");

    mixin("private void " ~ fname ~ "(T ...)(T params) {"
            " if(m__" ~ settername ~ " !is null && m__" ~ settername ~ ".isValid) m__" ~ settername ~ ".call(params);}");

    mixin("mixin BindMethod!(" ~ settername ~ ");");
    mixin("mixin BindMethod!(" ~ gettername ~ ");");
}

template BindHandler(alias func)
{
    mixin BindHandler!("on_" ~ MethodName!(func)[1..$] ~ "",
                       "get_" ~ MethodName!(func)[1..$] ~ "Handler",
                       MethodName!(func)[1..$] ~ "Callback",
                       func);
}

/**
  Multiple version for BindHandlers

  Generate a method wrapper for the given list of methods.
  Automatically generates a lispy names for them.
*/
template BindHandlers(T ...)
{
  static if (T.length == 1) {
    mixin BindHandler!(T[0]);
  } else {
    mixin BindHandler!(T[0]);
    mixin BindHandlers!(T[1..$]);
  }
}

/**
  Generate a method wrapper for the given method.
  You can pass a name as first argument, that will
  be exported to DLisp.
*/
template BindMethod(string name,alias func)
{
    static this()
    {
//       writefln("DLISP.BIND: method %s of class %s",toupper(name), toupper(typeof(this).stringof) );
      static Cell* methodWrapper(IDLisp dlisp, Cell* cell)
      {
        assert(cell !is null);
        if(cell.cdr is null)
            throw new ArgumentState("Method " ~ name ~ " called without object.",cell.pos);

        Cell* objectCell = cell.cdr.car;
        Cell* evalObjectCell = dlisp.eval(objectCell);
        auto instance = getInstance(evalObjectCell);
        if(instance is null)
            throw new ArgumentState("Method " ~ name ~ " called without a valid object, got " ~ cellToString(evalObjectCell),cell.pos);

        // writefln ("METHOD: %s SELF: %s ARGS: %s",name, instance, cellToString(cell.cdr.cdr));

        Cell*[] cargs = evalArgs(dlisp,cell.cdr.cdr);
        static if( HasParams!(func) ) {
          const int param_length = ParameterTypeTuple!(func).length;
        } else {
          const int param_length = 0;
        }

        if(param_length > cargs.length)
        {
          throw new ArgumentState("Function " ~ name ~ " got too few arguments.",cell.pos);
        }

        if(param_length < cargs.length)
        {
          throw new ArgumentState("Function " ~ name ~ " got too many arguments.",cell.pos);
        }

        static if( param_length ) {
          Box[] args;
          mixin(BoxArguments!(ParameterTypeTuple!(func)));
          mixin(InvokeMethod!(func));
        } else {
          static if( HasReturnType!(func) ){
            mixin("auto return_value = instance." ~ func.stringof ~ ";");
          } else {
            mixin("instance." ~ func.stringof ~ ";");
          }
        }

        static if( HasReturnType!(func) ){
          mixin(BoxReturnValue!(ReturnType!(func)));
        }
        return objectCell;
      }
//       writefln(name);
      _methods[toupper(name)] = newPredef(name,toDelegate(&methodWrapper),"auto-generated unbound method.");
    }
}

/**
  Generate a lispy name from a camel case name.

  This is a compile-time evaluatable function
  used to translate names as "createTile" into
  "CREATE-TILE".
*/
string Lispify(string name)
{
  string new_name;
  bool lastup = true;
  for(int i= 1; i != name.length; ++i)
  {
    if( toupper(name[i..i+1]) == name[i..i+1] )
    {
        if( !lastup )
            new_name ~= '-';
        lastup = true;
    } else
       lastup = false;
    if( name[i] != '_' )
        new_name ~= name[i];
  }
  return toupper(new_name);
}

/**
  Generate a method wrapper for the given method.
  Automatically generates a lispy name for it.
*/
template BindMethod(alias func)
{
  mixin BindMethod!(Lispify(MethodName!(func)),func);
}

/**
  Multiple version for BindMethod

  Generate a method wrapper for the given list of methods.
  Automatically generates a lispy names for them.
*/
template BindMethods(T ...)
{
  static if (T.length == 1) {
    mixin BindMethod!(T[0]);
  } else {
    mixin BindMethod!(T[0]);
    mixin BindMethods!(T[1..$]);
  }
}

/**
  Generate a function wrapper for the given function.
  You can pass a name as first argument, that will
  be exported to DLisp.
*/
template BindFunction(string name,alias func)
{
    //  pragma(msg,name);
    static this()
    {
      static Cell* functionWrapper(IDLisp dlisp, Cell* cell)
      {
        Cell*[] cargs = evalArgs(dlisp,cell.cdr);

        if(ParameterTypeTuple!(func).length > cargs.length)
        {
          throw new ArgumentState("Function " ~ name ~ " got too few arguments.",cell.pos);
        }

        if(ParameterTypeTuple!(func).length < cargs.length)
        {
          throw new ArgumentState("Function " ~ name ~ " got too many arguments.",cell.pos);
        }

        Box[] args;
        mixin(BoxArguments!(ParameterTypeTuple!(func)));
        mixin(InvokeFunction!(func));

        static if( HasReturnType!(func) ){
          mixin(BoxReturnValue!(ReturnType!(func)));
        }
        return null;
      }

      _functions ~= newPredef(name,toDelegate(&functionWrapper),"auto-generated function.");
    }
}

/**
  Generate a function wrapper for the given function.
  Automatically generates a lispy name for it.
*/
template BindFunction(alias func)
{
  mixin BindFunction!(Lispify((&func).stringof[1..$]),func);
}

/**
  Multiple version for BindFunction

  Generate a Function wrapper for the given list of Functions.
  Automatically generates a lispy names for them.
*/
template BindFunctions(T ...)
{
  static if (T.length == 1) {
    mixin BindFunction!(T[0]);
  } else {
    mixin BindFunction!(T[0]);
    mixin BindFunctions!(T[1..$]);
  }
}


class FunctionSet
{
  private static Cell*[] _functions;

  static void bind(Environment environment)
  {
      foreach(Cell* cell; _functions)
      {
        environment[cell.name] = cell;
      }
  }
}



